CloudWatch GetMetricData でリクエストされているメトリクス内訳を CloudTrailログから把握する

CloudWatch GetMetricData でリクエストされているメトリクス内訳を CloudTrailログから把握する

Clock Icon2024.09.17

とある環境にて CloudWatchメトリクス取得の料金( GetMetricData )を最適化しようとしています。 以下のように継続的にコストになっています。

sc_2024-09-17_11-33-09_24575

この環境では Datadog のAWS統合(インテグレーション) を導入しています。 AWS統合により CloudWatchメトリクスが収集されるのですが、これがコストとなっています。

このコストを抑えたいのですが、少し情報が足りません。 実際に「 どの名前空間(やメトリクス) にリクエストがされているのか 」、内訳を知りたいです。

悩んでいたところ以下ブログにて GetMetricData ログを取得できるようになったと知りました。

https://dev.classmethod.jp/articles/cloudwatch-getmetricdata-api-cloudtrail-event/

「これを使って調査しよう」、と思い立って本ブログを書きました。

とりあえずGetMetricDataログを取ってみる

CloudTrailデータイベントを有効化して GetMetricDataログを取得するようにします。 (手順は いわささんのアップデートブログ に丁寧に記載されているので割愛します)

CloudWatch Logs に数日分の GetMetricDataログを出力します。 以下のように Datadogからのログを確認できました。

sc_2024-09-13_10-36-16_9573

こんな感じ(↓)のログが大量に出力されます。

GetMetricDataログのサンプル
{
  "eventVersion": "1.10",
  "userIdentity": {
    "type": "AssumedRole",
    "principalId": "AROAEXAMPLE:DatadogAWSIntegration",
    "arn": "arn:aws:sts::123456789012:assumed-role/DatadogIntegrationRole/DatadogAWSIntegration",
    "accountId": "123456789012",
    "accessKeyId": "ASIAEXAMPLE",
    "sessionContext": {
      "sessionIssuer": {
        "type": "Role",
        "principalId": "AROAEXAMPLE",
        "arn": "arn:aws:iam::123456789012:role/DatadogIntegrationRole",
        "accountId": "123456789012",
        "userName": "DatadogIntegrationRole"
      },
      "attributes": {
        "creationDate": "2024-08-18T19:14:00Z",
        "mfaAuthenticated": "false"
      }
    }
  },
  "eventTime": "2024-08-18T19:48:13Z",
  "eventSource": "monitoring.amazonaws.com",
  "eventName": "GetMetricData",
  "awsRegion": "us-east-1",
  "sourceIPAddress": "X.X.X.X",
  "userAgent": "python-urllib3/X.X.X",
  "requestParameters": {
    "metricDataQueries": [
      {
        "id": "m872",
        "metricStat": {
          "metric": {
            "namespace": "AWS/Usage",
            "metricName": "CallCount",
            "dimensions": [
              {
                "name": "Resource",
                "value": "RefreshTrustedAdvisorCheck"
              },
              {
                "name": "Type",
                "value": "API"
              },
              {
                "name": "Service",
                "value": "Support"
              },
              {
                "name": "Class",
                "value": "None"
              }
            ]
          },
          "period": 60,
          "stat": "Maximum"
        }
      },
      {
        "id": "m873",
        "metricStat": {
          "metric": {
            "namespace": "AWS/Usage",
            "metricName": "CallCount",
            "dimensions": [
              {
                "name": "Resource",
                "value": "RefreshTrustedAdvisorCheck"
              },
              {
                "name": "Type",
                "value": "API"
              },
              {
                "name": "Service",
                "value": "Support"
              },
              {
                "name": "Class",
                "value": "None"
              }
            ]
          },
          "period": 60,
          "stat": "Minimum"
        }
      },
      {
        "id": "m874",
        "metricStat": {
          "metric": {
            "namespace": "AWS/Usage",
            "metricName": "CallCount",
            "dimensions": [
              {
                "name": "Resource",
                "value": "RefreshTrustedAdvisorCheck"
              },
              {
                "name": "Type",
                "value": "API"
              },
              {
                "name": "Service",
                "value": "Support"
              },
              {
                "name": "Class",
                "value": "None"
              }
            ]
          },
          "period": 60,
          "stat": "Sum"
        }
      },
      {
        "id": "m875",
        "metricStat": {
          "metric": {
            "namespace": "AWS/Usage",
            "metricName": "CallCount",
            "dimensions": [
              {
                "name": "Class",
                "value": "None"
              },
              {
                "name": "Resource",
                "value": "SelectResourceConfig"
              },
              {
                "name": "Type",
                "value": "API"
              },
              {
                "name": "Service",
                "value": "AWS Config"
              }
            ]
          },
          "period": 60,
          "stat": "Average"
        }
      },
      {
        "id": "m876",
        "metricStat": {
          "metric": {
            "namespace": "AWS/Usage",
            "metricName": "CallCount",
            "dimensions": [
              {
                "name": "Class",
                "value": "None"
              },
              {
                "name": "Resource",
                "value": "SelectResourceConfig"
              },
              {
                "name": "Type",
                "value": "API"
              },
              {
                "name": "Service",
                "value": "AWS Config"
              }
            ]
          },
          "period": 60,
          "stat": "Maximum"
        }
      },
      {
        "id": "m877",
        "metricStat": {
          "metric": {
            "namespace": "AWS/Usage",
            "metricName": "CallCount",
            "dimensions": [
              {
                "name": "Class",
                "value": "None"
              },
              {
                "name": "Resource",
                "value": "SelectResourceConfig"
              },
              {
                "name": "Type",
                "value": "API"
              },
              {
                "name": "Service",
                "value": "AWS Config"
              }
            ]
          },
          "period": 60,
          "stat": "Minimum"
        }
      },
      {
        "id": "m878",
        "metricStat": {
          "metric": {
            "namespace": "AWS/Usage",
            "metricName": "CallCount",
            "dimensions": [
              {
                "name": "Class",
                "value": "None"
              },
              {
                "name": "Resource",
                "value": "SelectResourceConfig"
              },
              {
                "name": "Type",
                "value": "API"
              },
              {
                "name": "Service",
                "value": "AWS Config"
              }
            ]
          },
          "period": 60,
          "stat": "Sum"
        }
      }
    ],
    "startTime": "Aug 18, 2024, 7:28:10 PM",
    "endTime": "Aug 18, 2024, 7:48:13 PM"
  },
  "responseElements": null,
  "requestID": "5160example-xxxx",
  "eventID": "8a44example-xxxx",
  "readOnly": true,
  "resources": [
    {
      "type": "AWS::CloudWatch::Metric"
    }
  ],
  "eventType": "AwsApiCall",
  "managementEvent": false,
  "recipientAccountId": "123456789012",
  "eventCategory": "Data",
  "tlsDetails": {
    "tlsVersion": "TLSv1.3",
    "cipherSuite": "TLS_AES_128_GCM_SHA256",
    "clientProvidedHostHeader": "monitoring.us-east-1.amazonaws.com"
  }
}

GetMetricDataログを加工、集計してみる

GetMetricData の料金は、「このAPIが叩かれた回数」 ではなく 「リクエストされたメトリクス数」です。

GetMetricData、GetInsightRuleReport USD 0.01/リクエストされた 1,000 個のメトリクス

– 引用: 料金 - Amazon CloudWatch | AWS

なので、このログから「どんなメトリクスがどれだけリクエストされているか」を見る必要があります。

CloudWatch Logs Insight でログをダウンロード

CloudWatch Logs Insight にて、1日分の GetMetricDataログを取得します。

1日分の範囲を指定して、以下クエリを実行します。

fields @timestamp, @message
| filter (
    userIdentity.sessionContext.sessionIssuer.userName = "DatadogIntegrationRole"
    and eventName = "GetMetricData" )
| sort @timestamp desc
| limit 10000

sc_2024-09-17_10-26-41_32234

[テーブルをダウンロード(JSON)] を選択して、JSONデータをローカルに保存します。

sc_2024-09-17_10-27-49_22416

以下のようなJSONです。

[
    {
        "@timestamp": "2024-08-16 14:53:16.675",
        "@message": {
            "eventVersion": "1.09",
            "userIdentity": {
                "type": "AssumedRole",
                "principalId": "AROAEXAMPLE:DatadogAWSIntegration",
                "arn": "arn:aws:sts::123456789012:assumed-role/DatadogIntegrationRole/DatadogAWSIntegration",
                "accountId": "123456789012",
                # ... 略

jq で集計

得られたログを jq コマンド (ほか) を使って集計します。

jq クエリで「リクエストされた名前空間(namespace)」を列挙してみます。

jq -r '.[]."@message".requestParameters.metricDataQueries[]?.metricStat.metric|.namespace' ./logs-insights-results.json \
| head
# AWS/Config
# AWS/Config
# AWS/Config
# AWS/Config
# AWS/Config
# AWS/Config
# AWS/Config
# AWS/Config
# AWS/Config
# AWS/Config

これを sort および uniq コマンドで集計します。

jq -r '.[]."@message".requestParameters.metricDataQueries[]?.metricStat.metric|.namespace' ./logs-insights-results.json \
| sort | uniq -c | sort -nr
# 48730 AWS/TrustedAdvisor
# 22249 AWS/Usage
# 8707 AWS/Config
# 5464 AWS/States
# 2880 AWS/DynamoDB
#  447 AWS/Events
#  370 AWS/Logs
#  342 AWS/Firehose
#  244 AWS/Location
#  164 AWS/SNS
#  122 AWS/SecretsManager
#   81 AWS/S3
#   48 AWS/HealthLake

意外にも Trusted Advisor 関連のメトリクスのリクエストが多かったです。 グラフにするとこんな感じ。

sc_2024-09-17_11-10-36_27065

また、「名前空間 > メトリクス名」でも集計してみます。

jq -r '.[]."@message".requestParameters.metricDataQueries[]?.metricStat.metric|[.namespace, .metricName]|@tsv' ./logs-insights-results.json \
| sort | uniq -c | sort -nr
# 43984 AWS/TrustedAdvisor  ServiceLimitUsage
# 18413 AWS/Usage   CallCount
# 8361 AWS/Config   ConfigurationRecorderInsufficientPermissionsFailure
# 3377 AWS/Usage    ResourceCount
# 2377 AWS/TrustedAdvisor   YellowResources
# 2369 AWS/TrustedAdvisor   RedResources
# 1366 AWS/States   ThrottledEvents
# 1366 AWS/States   ProvisionedRefillRate
# 1366 AWS/States   ProvisionedBucketSize
# 1366 AWS/States   ConsumedCapacity
#  432 AWS/DynamoDB MaxProvisionedTableWriteCapacityUtilization
#  432 AWS/DynamoDB MaxProvisionedTableReadCapacityUtilization
#  432 AWS/DynamoDB AccountProvisionedWriteCapacityUtilization
#  432 AWS/DynamoDB AccountProvisionedReadCapacityUtilization
#  274 AWS/Config   ConfigurationItemsRecorded
# ...略

こちらもグラフにしてみました。

sc_2024-09-17_11-12-22_24161

注意点: リクエストパラメータが省略されることがある

リクエストパラメータのサイズが大きいと、以下のようにログの中身が省略されます。

"requestParameters": {
    "omitted": true,
    "originalSize": 133746,
    "reason": "requestParameters too large"
},

この件はCloudTrailのユーザーガイドにも記載があります。

requestParameters

リクエストとともに送信されたパラメータ (ある場合)。 これらのパラメータは、適切な AWS サービスのAPIリファレンスドキュメントに記載されています。 このフィールドの最大サイズは 100 KB です。この制限を超えるコンテンツは切り捨てられます。

– 引用: CloudTrail レコードの内容 - AWS CloudTrail

今回だと 1761件のうち 361件が省略されていました。 厳密な分析ができないのは少しモヤモヤしますが、これに関しては仕方ないですね。

おわりに

以上、GetMetricDataログからメトリクス内訳を確認しました。

ちなみに、このあと Datadog から AWS/TrustedAdvisor メトリクスを収集しないように設定したところ、 見積もりどおり GetMetricData コストが削減されました。

sc_2024-09-17_11-21-05_2625

リクエストパラメータの一部省略は少しかゆいところですが、 「どの名前空間のメトリクスがリクエストされているか」をざっくり把握したいときは役に立ちそうです。

以上、参考になれば幸いです。

参考

https://dev.classmethod.jp/articles/amazon-cloudwatch-getmetricdata-api-cost/

https://dev.classmethod.jp/articles/cloudwatch-getmetricdata-api-cloudtrail-event/

https://docs.datadoghq.com/ja/integrations/amazon_web_services/

https://docs.datadoghq.com/ja/integrations/guide/aws-integration-and-cloudwatch-faq/

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.